{$MODE FPC}
{$MODESWITCH Result}
{$MODESWITCH Autoderef}
{$MODESWITCH Out}
unit dcreole;

interface

type
PCreoleParser = ^TCreoleParser;

PCreoleProcess = ^TCreoleProcess;
TCreoleProcess = object
public
  // Paragraph
  procedure StartParagraph(C: PCreoleParser); virtual;
  procedure FinishParagraph(C: PCreoleParser); virtual;
  // Processes simple text
  // P - pointer to chars
  // Count - number of bytes (not chars!) in string
  procedure Simple(C: PCreoleParser; P: PAnsiChar; Count: Integer); virtual;
  // {{{Inline unformatted text}}}
  procedure Unformat(C: PCreoleParser; P: PAnsiChar; Count: Integer); virtual;
  // {{{
  // Multiline unformatted text line
  // }}}
  procedure StartNowiki(C: PCreoleParser); virtual;
  procedure FinishNowiki(C: PCreoleParser); virtual;
  // End of line in input string
  procedure EndOfLine(C: PCreoleParser); virtual;
  // Break line//here
  procedure LineBreak(C: PCreoleParser); virtual;
  // **Bold text**
  procedure StartBold(C: PCreoleParser); virtual;
  procedure FinishBold(C: PCreoleParser); virtual;
  // //italics text//
  procedure StartItalics(C: PCreoleParser); virtual;
  procedure FinishItalics(C: PCreoleParser); virtual;
  // Bullet list
  // * item1 of level 1
  // * item2 of level 1
  // ** item of level 2
  // C^.Level - level of current list
  procedure StartBulletList(C: PCreoleParser); virtual;
  procedure FinishBulletList(C: PCreoleParser); virtual;
  procedure StartBulletItem(C: PCreoleParser); virtual;
  procedure FinishBulletItem(C: PCreoleParser); virtual;
  // Numbered list
  // # item1 of level 1
  // # item2 of level 1
  // ## item of level 2
  // C^.Level - level of current list
  procedure StartNumList(C: PCreoleParser); virtual;
  procedure FinishNumList(C: PCreoleParser); virtual;
  procedure StartNumItem(C: PCreoleParser); virtual;
  procedure FinishNumItem(C: PCreoleParser); virtual;
  // Link
  procedure Link(C: PCreoleParser; Address, Title: String); virtual;
  // Heading
  // == Largest heading         - Level=1
  // === Large heading          - Level=2
  // ==== Medium heading        - Level=3
  // ===== Small heading        - Level=4
  // ====== Very small heading  - Level=5
  // ======= Smallest heading   - Level=6
  procedure StartHeading(C: PCreoleParser; Level: Integer); virtual;
  procedure FinishHeading(C: PCreoleParser; Level: Integer); virtual;
  // Horizontal line
  // ----
  procedure Horizontal(C: PCreoleParser); virtual;
  // Image
  procedure Image(C: PCreoleParser; Address, Title: String); virtual;
  // Table
  // |= the |= table |= header |
  // | a | table | row |
  // | b | table | row |
  // C^.Row - current row (in Row and Cell methods)
  // C^.Column - current row (in Cell methods)
  procedure StartTable(C: PCreoleParser); virtual;
  procedure FinishTable(C: PCreoleParser); virtual;
  procedure StartRow(C: PCreoleParser); virtual;
  procedure FinishRow(C: PCreoleParser); virtual;
  procedure StartCell(C: PCreoleParser); virtual;
  procedure FinishCell(C: PCreoleParser); virtual;
  procedure StartHeadingCell(C: PCreoleParser); virtual;
  procedure FinishHeadingCell(C: PCreoleParser); virtual;

  // <<<Plugin>>>
  procedure Plugin(C: PCreoleParser; P: PAnsiChar; Count: Integer); virtual;
end;

TCreoleParser = object
private
  FProcess: PCreoleProcess;
  // Is text paragraph?
  FParagraph: Boolean;
  // NoWiki
  FNoWiki: Boolean;
  // Bold/italics status
  FItalics, FBold: Boolean;
  // List status
  FLevel: Integer;
  FIsListItem: Boolean;
  // Bit mask that specifies ordered or unordered list
  FIsOrdered: Integer;
  // Table status
  FTable: Boolean;
  FRow, FCol: Integer;
  // Check IsOrdered by Level
  function IsOrdered(Level: Integer): Boolean;
  // Some raw input events
  procedure Bold; inline;
  procedure Italics; inline;
  //procedure NewLine; inline;
  //procedure EmptyLine; inline;
  //procedure InputLinkStart;
  procedure BeginParagraph; inline;
  procedure EndParagraph; inline;
  procedure EndListItem; inline;
  procedure EndList; inline;
  procedure EndLists; inline;
  procedure BeginTable; inline;
  procedure EndTable; inline;
  procedure LineBreak; inline;
  function NormalizeUrl(const Address: AnsiString): AnsiString;
  procedure TryToDetectUrl(const Line: AnsiString;
      var Start, Slash: Integer;
      out IsUrl: Boolean; out AfterUrl: Integer);
  procedure ParseHeading(const Line: AnsiString; I: Integer); inline;
  procedure ParseStar(const Line: AnsiString; I: Integer); inline;
  procedure ParseHash(const Line: AnsiString; I: Integer); inline;
  procedure ParseTable(const Line: AnsiString; I: Integer); inline;
  procedure ParseTableItem(const Line: AnsiString; var I: Integer); inline;
  function ParseLink(const Line: AnsiString; I: Integer): Integer; inline;
  function ParseImage(const Line: AnsiString; I: Integer): Integer; inline;
  function ParseInline(const Line: AnsiString; I: Integer): Integer; inline;
  function ParsePlugin(const Line: AnsiString; I: Integer): Integer; inline;
  function FormattedLine(const Line: AnsiString; I: Integer): Integer;
  procedure ParseLine(const Line: AnsiString);
public
  // single function way, Init & Done will be called inside
  procedure Parse(var F: TextFile; Process: PCreoleProcess);
  procedure Parse(const FileName: String; Process: PCreoleProcess);
  // per line mode
  constructor Init(Process: PCreoleProcess);
  destructor Done;
  // status
  property Level: Integer read FLevel;
  property Row: Integer read FRow;
  property Col: Integer read FCol;
end;

implementation

procedure TCreoleProcess.StartParagraph(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishParagraph(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.Simple(C: PCreoleParser; P: PAnsiChar; Count: Integer);
begin
end;

procedure TCreoleProcess.Unformat(C: PCreoleParser; P: PAnsiChar; Count: Integer);
begin
end;

procedure TCreoleProcess.StartNowiki(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishNowiki(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.EndOfLine(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.LineBreak(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartBold(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishBold(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartItalics(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishItalics(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartBulletList(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishBulletList(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartBulletItem(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishBulletItem(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartNumList(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishNumList(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartNumItem(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishNumItem(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.Link(C: PCreoleParser; Address, Title: String);
begin
end;

procedure TCreoleProcess.StartHeading(C: PCreoleParser; Level: Integer);
begin
end;

procedure TCreoleProcess.FinishHeading(C: PCreoleParser; Level: Integer);
begin
end;

procedure TCreoleProcess.Horizontal(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.Image(C: PCreoleParser; Address, Title: String);
begin
end;

procedure TCreoleProcess.StartTable(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishTable(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartRow(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishRow(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartCell(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishCell(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.StartHeadingCell(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.FinishHeadingCell(C: PCreoleParser);
begin
end;

procedure TCreoleProcess.Plugin(C: PCreoleParser; P: PAnsiChar; Count: Integer);
begin
end;

function TCreoleParser.IsOrdered(Level: Integer): Boolean;
begin
  Result := FIsOrdered and (1 shl Level) > 0;
end;

procedure TCreoleParser.Bold;
begin
  if FBold then begin
    FProcess.FinishBold(@Self);
    FBold := False;
  end else begin
    FProcess.StartBold(@Self);
    FBold := True;
  end;
end;

procedure TCreoleParser.Italics;
begin
  if FItalics then begin
    FProcess.FinishItalics(@Self);
    FItalics := False;
  end else begin
    FProcess.StartItalics(@Self);
    FItalics := True;
  end;
end;

const
  BLANK = [' ', #9];

function SkipBlank(const Line: String): Integer; inline;
begin
  Result := 1;
  while (Result <= Length(Line)) and (Line[Result] in BLANK) do
    Inc(Result);
end;

function IsEmpty(const Line: String; Start: Integer): Boolean; inline;
begin
  Result := Start > Length(Line);
end;

function LastNonBlank(const Line: String): Integer; inline;
begin
  Result := Length(Line);
  while (Result > 0) and (Line[Result] in BLANK) do
    Dec(Result);
end;

function IsPrefix(const S, P: String): Boolean; inline;
begin
  Exit((Length(S) >= Length(P)) and (CompareByte(S[1], P[1], Length(P)) = 0));
end;

// In inline nowiki, any trailing closing brace is included in the nowiki
// span (i.e. in a sequence of more than three closing braces, the end marker
// is made of the last three braces). 
// TODO

// In preformatted blocks, since markers must not be preceded by leading
// spaces, lines with three closing braces which belong to the
// preformatted block must follow at least one space. In the rendered
// output, one leading space is removed. 
function NeedRemoveLeadingSpace(const Line: AnsiString): Boolean; inline;
var
  I: Integer;
begin
  Result := False;
  I := 1;
  while (I <= Length(Line)) and (Line[I] = ' ') do
    Inc(I);
  if Length(Line) < I + 2 then
    Exit;
  Result := (Line[I] = '}') and (Line[I + 1] = '}') and (Line[I + 1] = '}');
end;

procedure TCreoleParser.BeginParagraph;
begin
  if not FParagraph then begin
    FProcess.StartParagraph(@Self);
    FParagraph := True;
  end;
end;

procedure TCreoleParser.EndParagraph;
begin
  // TODO: remember bold and italics order
  if FBold then begin
    FProcess.FinishBold(@Self);
    FBold := False;
  end;
  if FItalics then begin
    FProcess.FinishItalics(@Self);
    FItalics := False;
  end;
  if FParagraph then begin
    FProcess.FinishParagraph(@Self);
    FParagraph := False;
  end;
end;

procedure TCreoleParser.EndListItem;
begin
  if FIsListItem then begin
    if IsOrdered(FLevel) then
      FProcess.FinishNumItem(@Self)
    else
      FProcess.FinishBulletItem(@Self);
    FIsListItem := False;
  end;
end;

procedure TCreoleParser.EndList;
begin
  EndListItem;
  if IsOrdered(FLevel) then
    FProcess.FinishNumList(@Self)
  else
    FProcess.FinishBulletList(@Self);
  Dec(FLevel);
end;

procedure TCreoleParser.EndLists;
begin
  while FLevel > 0 do
    EndList;
end;

procedure TCreoleParser.BeginTable;
begin
  if not FTable then begin
    FProcess.StartTable(@Self);
    FTable := True;
  end;
end;

procedure TCreoleParser.EndTable;
begin
  if FTable then begin
    FProcess.FinishTable(@Self);
    FTable := False;
  end;
end;

procedure TCreoleParser.LineBreak;
begin
  FProcess.LineBreak(@Self);
end;

function TestTwoChars(const S: AnsiString; I: Integer; B, C: AnsiChar): Boolean;
begin
  Result := (I + 1 <= Length(S)) and (S[I] = B) and (S[I] = C);
end;

function TCreoleParser.NormalizeUrl(const Address: AnsiString): AnsiString;
begin
  // TODO :)
  Result := Address;
end;

procedure TCreoleParser.TryToDetectUrl(const Line: AnsiString;
    var Start, Slash: Integer;
    out IsUrl: Boolean; out AfterUrl: Integer);
const
  PUNCTUATION = [',', '.', '?', '!', ':', ';', '"', ''''];
var
  I: Integer;
begin
  IsUrl := False;
  AfterUrl := Slash + 2;
  if Slash - Start >= 5 then begin
    // mainly we should test for http:// scheme
    if CompareByte(Line[Slash - 5], 'http:', 5) = 0 then begin
      IsUrl := True;
      Slash := Slash - 5;
    end;
  end else begin
  end;
  while AfterUrl <= Length(Line) do
    // any other url breakers?
    if (Line[AfterUrl] = #32) or (Line[AfterUrl] = #9) then
      Break
    else
      Inc(AfterUrl);
  if Line[AfterUrl - 1] in PUNCTUATION then
    Dec(AfterUrl);
end;

procedure TCreoleParser.ParseHeading(const Line: AnsiString; I: Integer);
var
  L, J: Integer;
begin
  EndTable;
  EndLists;
  L := 1;
  Inc(I);
  while (I <= Length(Line)) and (Line[I] = '=') do begin
    Inc(I);
    Inc(L);
  end;
  if L = 0 then begin
    FormattedLine(Line, 1);
    Exit;
  end;
  while (I <= Length(Line)) and (Line[I] in BLANK) do
    Inc(I);
  if I > Length(Line) then
    Exit; // empty heading, nice...
  J := Length(Line);
  while (J >= 1) and (Line[J] in BLANK) do
    Dec(J);
  while (J >= 1) and (Line[J] = '=') do
    Dec(J);
  while (J >= 1) and (Line[J] in BLANK) do
    Dec(J);
  EndParagraph;
  FProcess.StartHeading(@Self, L);
  FProcess.Simple(@Self, @Line[I], J - I + 1);
  FProcess.FinishHeading(@Self, L);
end;

procedure TCreoleParser.ParseStar(const Line: AnsiString; I: Integer);
var
  L, J: Integer;
begin
  EndParagraph;
  EndTable;
  L := 1;
  Inc(I);
  while (I <= Length(Line)) and (Line[I] = '*') do begin
    Inc(I);
    Inc(L);
  end;
  if L = 2 then begin
    // check if it is bold marker
    if FLevel = 0 then begin
      FormattedLine(Line, 1);
      Exit;
    end;
  end;
  EndListItem;
  if L > FLevel then begin
    for J := FLevel + 1 to L do begin
      FProcess.StartBulletList(@Self);
      FIsOrdered := FIsOrdered and not (1 shl J);
    end;
    FLevel := L;
  end else while L < FLevel do
    EndList;
  FProcess.StartBulletItem(@Self);
  FIsListItem := True;
  FormattedLine(Line, I);
  FProcess.EndOfLine(@Self);
end;

// just copypasted from ParseStar, don't touch it
procedure TCreoleParser.ParseHash(const Line: AnsiString; I: Integer);
var
  L, J: Integer;
begin
  EndParagraph;
  EndTable;
  L := 1;
  Inc(I);
  while (I <= Length(Line)) and (Line[I] = '#') do begin
    Inc(I);
    Inc(L);
  end;
  EndListItem;
  if L > FLevel then begin
    for J := FLevel + 1 to L do begin
      FProcess.StartNumList(@Self);
      FIsOrdered := FIsOrdered or (1 shl J);
    end;
    FLevel := L;
  end else while L < FLevel do
    EndList;
  FProcess.StartNumItem(@Self);
  FIsListItem := True;
  FormattedLine(Line, I);
  FProcess.EndOfLine(@Self);
end;

procedure TCreoleParser.ParseTable(const Line: AnsiString; I: Integer);
var
  Last: Integer;
begin
  BeginTable;
  FProcess.StartRow(@Self);
  Last := LastNonBlank(Line);
  if Line[Last] <> '|' then
    Inc(Last);
  while I < Last do
    ParseTableItem(Line, I);
  FProcess.FinishRow(@Self);
end;

procedure TCreoleParser.ParseTableItem(const Line: AnsiString; var I: Integer);
var
  Heading: Boolean;
begin
  Heading := Line[I + 1] = '=';
  if not Heading then begin
    Inc(I);
    FProcess.StartCell(@Self);
  end else begin
    Inc(I, 2);
    FProcess.StartHeadingCell(@Self);
  end;
  I := FormattedLine(Line, I);
  if not Heading then
    FProcess.FinishCell(@Self)
  else
    FProcess.FinishHeadingCell(@Self);
end;

function TCreoleParser.ParseLink(const Line: AnsiString; I: Integer): Integer;
label
  MiddleOfExpression, EndOfExpression;
var
  J, K: Integer;
  Address, Title: AnsiString;
begin
  // skip [[
  Inc(I, 2);

  Address := '';
  J := I;
  while J <= Length(Line) do
    if Line[J] = '|' then begin
      Address := Copy(Line, I, J - I);
      Inc(J);
      I := J;
      goto MiddleOfExpression;
    end else if TestTwoChars(Line, J, ']', ']') then begin
      goto EndOfExpression;
    end else
      Inc(J);

MiddleOfExpression:
  while J <= Length(Line) do
    if TestTwoChars(Line, J, ']', ']') then
      goto EndOfExpression
    else
      Inc(J);

EndOfExpression:
  Title := Copy(Line, I, J - I);
  if Address = '' then
    Address := Title;
  Address := NormalizeUrl(Address);
  FProcess.Link(@Self, Address, Title);
  Result := J + 2;
end;

// just copypasted from ParseLink, don't touch it
function TCreoleParser.ParseImage(const Line: AnsiString; I: Integer): Integer;
label
  MiddleOfExpression, EndOfExpression;
var
  J, K: Integer;
  Address, Title: AnsiString;
begin
  // skip [[
  Inc(I, 2);

  Address := '';
  J := I;
  while J <= Length(Line) do
    if Line[J] = '|' then begin
      Address := Copy(Line, I, J - I);
      Inc(J);
      I := J;
      goto MiddleOfExpression;
    end else if TestTwoChars(Line, J, '}', '}') then begin
      goto EndOfExpression;
    end else
      Inc(J);

MiddleOfExpression:
  while J <= Length(Line) do
    if TestTwoChars(Line, J, '}', '}') then
      goto EndOfExpression
    else
      Inc(J);

EndOfExpression:
  Title := Copy(Line, I, J - I);
  if Address = '' then begin
    Address := Title;
    Title := '';
  end;
  Address := NormalizeUrl(Address);
  FProcess.Image(@Self, Address, Title);
  Result := J + 2;
end;

function TCreoleParser.ParseInline(const Line: AnsiString; I: Integer): Integer;
var
  Start: Integer;
begin
  Start := I + 3;
  Result := Start;
  while Result + 2 <= Length(Line) do
    if Line[Result] <> '}' then begin
      Inc(Result);
    end else if (Line[Result + 1] = '}') and (Line[Result + 2] = '}') then begin
      FProcess.Unformat(@Self, @Line[Start], Result - Start);
      Inc(Result, 3);
      Exit;
    end else
      Inc(Result);
  FNoWiki := True;
  FProcess.StartNowiki(@Self);
  FProcess.Simple(@Self, @Line[Start], Length(Line) + 1 - Start);
  FProcess.EndOfLine(@Self);
  Result := Length(Line) + 1;
end;

// just copypasted from ParseInline, don't touch it ;)
function TCreoleParser.ParsePlugin(const Line: AnsiString; I: Integer): Integer;
var
  Start: Integer;
begin
  Start := I + 3;
  Result := Start;
  while Result + 2 <= Length(Line) do
    if Line[Result] <> '>' then begin
      Inc(Result);
    end else if (Line[Result + 1] = '>') and (Line[Result + 2] = '>') then begin
      FProcess.Plugin(@Self, @Line[Start], Result - Start);
      Inc(Result, 3);
      Exit;
    end else
      Inc(Result);
  // TODO multiline supporting
  FProcess.Plugin(@Self, @Line[Start], Length(Line) - Start);
  Result := Length(Line) + 1;
end;

function TCreoleParser.FormattedLine(const Line: AnsiString; I: Integer): Integer;
const
  STOP = ['*', '[', '/', '\', '{', '~', '<', '|'];
var
  J, AfterUrl: Integer;
  IsUrl, Escape: Boolean;
  Url: AnsiString;
begin
  if (not FTable) and (FLevel = 0) then
    if FParagraph then
      FProcess.EndOfLine(@Self)
    else
      BeginParagraph;

  // ok, let's go...
  IsUrl := False;
  Escape := False;
  while I <= Length(Line) do begin
    J := I;
    while (J <= Length(Line)) and not (Line[J] in STOP) do
      Inc(J);
    // check: // may be a part of url scheme, not italics
    if TestTwoChars(Line, J, '/', '/') then
      TryToDetectUrl(Line, I, J, IsUrl, AfterUrl);
    if J > I then
      FProcess.Simple(@Self, @Line[I], J - I);
    if IsUrl then begin
      if not Escape then begin
        Url := Copy(Line, J, AfterUrl - J);
        FProcess.Link(@Self, Url, Url);
      end else begin
        FProcess.Simple(@Self, @Line[J], AfterUrl - J);
        Escape := False;
      end;
      J := AfterUrl;
      // don't forget to switch it off
      IsUrl := False;
    end else if J <= Length(Line) then begin
      if Escape then // first we check if escape affects
        Escape := J = I;
      if Escape then begin // now Escape really means escape char
        // TODO fix it if Line[J+1] is multibyte utf-8
        FProcess.Simple(@Self, @Line[J], 1);
        Escape := False;
        Inc(J);
      end else case Line[J] of
        '*': if (J + 1 <= Length(Line)) and (Line[J + 1] = '*') then begin
               Bold;
               Inc(J, 2);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '/': if (J + 1 <= Length(Line)) and (Line[J + 1] = '/') then begin
               Italics;
               Inc(J, 2);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '[': if (J + 1 <= Length(Line)) and (Line[J + 1] = '[') then begin
               J := ParseLink(Line, J);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '\': if (J + 1 <= Length(Line)) and (Line[J + 1] = '\') then begin
               LineBreak;
               Inc(J, 2);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '{': if (J + 1 <= Length(Line)) and (Line[J + 1] = '{') then begin
               if (J + 2 <= Length(Line)) and (Line[J + 2] = '{') then begin
                 J := ParseInline(Line, J);
               end else
                 J := ParseImage(Line, J);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '~': begin
               Escape := True;
               Inc(J);
             end;
        '<': if (J + 2 <= Length(Line)) and (Line[J] = '<') and
                (Line[J + 1] = '<') and (Line[J + 2] = '<') then begin
               J := ParsePlugin(Line, J);
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
        '|': if FTable then begin
               Result := J;
               Exit;
             end else begin
               FProcess.Simple(@Self, @Line[J], 1);
               Inc(J);
             end;
      else
        // possible situation: TryToDetectUrl may skip ~http://
      end;
    end;
    I := J;
  end;
  Result := I;
end;

procedure TCreoleParser.ParseLine(const Line: AnsiString);
var
  I: Integer;
begin
  if FNoWiki then begin
    if not IsPrefix(Line, '}}}') then begin
      I := 1 + Ord(NeedRemoveLeadingSpace(Line));
      FProcess.Simple(@Self, @Line[I], Length(Line));
      FProcess.EndOfLine(@Self);
    end else begin
      FProcess.FinishNowiki(@Self);
      FNoWiki := False;
      ParseLine(Copy(Line, 4, Length(Line) - 3));
    end;
    Exit;
  end;

  I := SkipBlank(Line);
  if IsEmpty(Line, I) then begin
    EndTable;
    EndLists;
    EndParagraph;
  end else case Line[I] of
    '=': ParseHeading(Line, I);
    '*': ParseStar(Line, I);
    '#': ParseHash(Line, I);
    '|': ParseTable(Line, I);
  else if IsPrefix(Line, '{{{') then begin
    EndTable;
    FNoWiki := True;
    FProcess.StartNowiki(@Self); 
  end else if Line = '----' then begin
    EndTable;
    EndLists;
    EndParagraph;
    FProcess.Horizontal(@Self);
  end else begin // simple formated text
    EndTable;
    FormattedLine(Line, 1);
  end end;
end;

procedure TCreoleParser.Parse(var F: TextFile; Process: PCreoleProcess);
var
  L: AnsiString;
begin
  FProcess := Process;
  while not Eof(F) do begin
    Readln(F, L);
    ParseLine(L);
  end;
end;

procedure TCreoleParser.Parse(const FileName: String; Process: PCreoleProcess);
var
  F: TextFile;
begin
  Assign(F, FileName);
  {$I-}
  Reset(F);
  {$I+}
  if IOResult <> 0 then
    Exit;
  Parse(F, Process);
  Close(F);
end;

constructor TCreoleParser.Init(Process: PCreoleProcess);
begin
  FProcess := Process;
  FParagraph := False;
  FNoWiki := False;
  FBold := False;
  FItalics := False;
  FLevel := 0;
  FTable := False;
  FRow := 0;
  FCol := 0;
end;

destructor TCreoleParser.Done;
begin
end;

end.
